/**
 * AdvisorApplicator.java
 * 
 * Created on Jul 8, 2008 at 5:08:25 PM
 */
package org.lego4j;

import java.util.ArrayList;
import java.util.List;

import org.lego4j.internal.IAdvisable;
import org.lego4j.internal.SpringAdvisable;
import org.lego4j.retryonexception.RetryOnExceptionPolicyAdvisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;

/**
 * To be used by {@link BeanPostProcessor}s to apply a matching and valid {@link IValidableAdvisor} to a bean.
 *
 * @author <a href="alexguev@gmail.com">Alexei Guevara</>
 * @version $Revision:$ updated on $Date:$ by $Author:$
 */
public class AdvisorApplicator {
    
    private final ProxyConfig myProxyConfig;
    
    private final ClassLoader myBeanClassLoader;
    
    private final List<IValidableAdvisor> myValidableAdvisors;

    public static AdvisorApplicator createWithProxyConfigAndBeanClassLoader( ProxyConfig theProxyConfig, ClassLoader theBeanClassLoader ) {
        return new AdvisorApplicator( theProxyConfig, theBeanClassLoader );
    }
    
    private AdvisorApplicator( ProxyConfig theProxyConfig, ClassLoader theBeanClassLoader ) {
        myValidableAdvisors = new ArrayList<IValidableAdvisor>();
        
        //FIXME: use generic Advisor that takes an annotation and a method interceptor factory
        
        myValidableAdvisors.add( new RetryOnExceptionPolicyAdvisor() );
        
        myProxyConfig = theProxyConfig;
        myBeanClassLoader = theBeanClassLoader;
    }
    
    /**
     * @param theBean
     */
    public Object apply( Object theBean ) throws BeanDefinitionValidationException {
        assert myBeanClassLoader != null || myProxyConfig != null  
            : "BeanPostprocessor must call initWithBeanClassLoaderAndProxyConfig before using apply!";
        
        Class<?> targetClass = targetClassOf(theBean);
        
        if (targetClass == null) {
            // Can't do much here
            return theBean;
        }
        
        return addAdvisors( theBean, targetClass );

    }

    private Object addAdvisors(Object theBean, Class<?> theBeanClass ) {
        IAdvisable advisable = new SpringAdvisable( theBean, theBeanClass, myProxyConfig, myBeanClassLoader );
        
        for (IValidableAdvisor advisor : myValidableAdvisors ) {
            if ( advisable.canApply( advisor) ) {
                advisable.addAdvisor( advisor );
            }
        }
        
        return advisable.getAdvicedBean();
    }

    private Class<?> targetClassOf(Object theBean) {
        return (theBean instanceof Advised ? ((Advised) theBean).getTargetSource().getTargetClass() : theBean.getClass());
    }

}
